#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

#include "net.h"
#include "quark.h"

quark_t *quark_sentinel() {
  quark_t *q;
  q = calloc(1, sizeof(*q));
  assert(q);
  q->syntax = SENTINEL_QUARK;
  return q;
}

int quark_cmp(const quark_t *q1, const quark_t *q2) {
  int partial_cmp;
  if (q1->syntax == SENTINEL_QUARK)
    return -1;
  if (q2->syntax == SENTINEL_QUARK)
    return 1;
  if (q1->segment.iov_len < q2->segment.iov_len) {
    partial_cmp = memcmp(q1->segment.iov_base, q2->segment.iov_base, q1->segment.iov_len);
    if (partial_cmp == 0)
      return -1;
    else
      return partial_cmp;
  }
  else if (q2->segment.iov_len < q1->segment.iov_len) {
    partial_cmp = memcmp(q1->segment.iov_base, q2->segment.iov_base, q2->segment.iov_len);
    if (partial_cmp == 0)
      return 1;
    else
      return partial_cmp;
  }
  return memcmp(q1->segment.iov_base, q2->segment.iov_base, q1->segment.iov_len);
}

static size_t _next_non_alnum(const char *str, size_t size) {
  int i;
  for (i = 0; i < size; i++) {
    if (!isalnum(str[i]))
      return i;
  }
  return size;
}

static size_t _next_alnum(const char *str, size_t size) {
  int i;
  for (i = 0; i < size; i++) {
    if (isalnum(str[i]))
      return i;
  }
  return size;
}

static quark_t *_quark_create(const char *str, size_t size) {
  quark_t *q;

  q = calloc(1, sizeof(*q));
  assert(q);

  q->segment.iov_base = (char *) str;
  q->segment.iov_len = size;

  return q;
}

static chunked_packet_t *_chunked_packet(packet_t *packet, int how) {
  chunked_packet_t *cpacket;
  const char *str;
  size_t off, size, len;
  quark_t *q;

  cpacket = calloc(1, sizeof(*cpacket));
  assert(cpacket);
  cpacket->packet = packet;
  TAILQ_INIT(&cpacket->quarks);

  str = packet->segment.iov_base;
  size = packet->segment.iov_len;

  for (off = 0; off < size; ) {
    if (how)
      len = 1;
    else
      len = _next_non_alnum(str + off, size - off);
    if (len > 0) {
      q = _quark_create(str + off, len);
      q->packet = cpacket;
      q->syntax = ALNUM_QUARK;
      TAILQ_INSERT_TAIL(&cpacket->quarks,q,next_by_packet);
      cpacket->n_quarks++;

      off += len;
    }
    if (off < size) {
      if (how)
        len = 1;
      else
        len = _next_alnum(str + off, size - off);
      assert(len >= 1);
      q = _quark_create(str + off, len);
      q->packet = cpacket;
      q->syntax = NONALNUM_QUARK;
      TAILQ_INSERT_TAIL(&cpacket->quarks,q,next_by_packet);
      cpacket->n_quarks++;

      off += len;
    }
  }

  return cpacket;
}

struct chunked_flow {
  flow_t *flow;
  TAILQ_HEAD(,chunked_packet) packets;
  TAILQ_ENTRY(chunked_flow) next;
};

TAILQ_HEAD(chunked_flowq,chunked_flow);

static struct chunked_flow *_cflow_lookup(struct chunked_flowq *q, flow_t *flow) {
  struct chunked_flow *cflow;
  TAILQ_FOREACH(cflow,q,next) {
    if (cflow->flow == flow)
      return cflow;
  }
  cflow = calloc(1, sizeof(*cflow));
  assert(cflow);
  cflow->flow = flow;
  TAILQ_INIT(&cflow->packets);
  TAILQ_INSERT_TAIL(q,cflow,next);
  return cflow;
}

chunked_packets_t *chunked_packets_tokenize(flowset_t *set, int how) {
  packet_t *packet;
  chunked_packet_t *cpacket;
  chunked_packets_t *cpackets;
  struct chunked_flow *cflow;
  struct chunked_flowq cflows;
  int n_packets = 0;

  cpackets = calloc(1, sizeof(*cpackets));
  assert(cpackets);
  cpackets->set = set;
  TAILQ_INIT(&cpackets->packets);

  TAILQ_INIT(&cflows);

  TAILQ_FOREACH(packet,&set->packets,next_by_time) {
    cpacket = _chunked_packet(packet, how);
    assert(cpacket->packet);
    TAILQ_INSERT_TAIL(&cpackets->packets,cpacket,next_by_time);

    cflow = _cflow_lookup(&cflows, packet->flow);
    TAILQ_INSERT_TAIL(&cflow->packets,cpacket,next_by_flow);

    n_packets++;
  }

  while ((cflow = TAILQ_FIRST(&cflows)) != NULL) {
    TAILQ_REMOVE(&cflows,cflow,next);
    free(cflow);
  }

  return cpackets;
}

chunked_packet_t *chunked_packets_by_flow(chunked_packets_t *packets, flow_t *flow) {
  chunked_packet_t *packet;

  TAILQ_FOREACH(packet,&packets->packets,next_by_time) {
    if (packet->packet->flow == flow)
      return packet;
  }
  return NULL;
}
